﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using PServiceBus.Core.Interface;
using PServiceBus.Core.Manager;
using System.Threading;
using PServiceBus.Core.Runtime;
using PServiceBus.Core.Logger;
using PServiceBus.Core.Runtime.Extensions;
using PServiceBus.Core.Runtime.Configuration;
using System.IO;
using System.Reflection;

namespace PServiceBus.TopicObservers {

    public class MefObserver : ITopicObserver {
        #region ITopicObserver Members

        private ManualResetEvent _reset = new ManualResetEvent(false);
        private List<Guid> _classIDs = new List<Guid>();

        [ImportMany(typeof(ITopic), AllowRecomposition=true)]
        protected IEnumerable<ITopic> _topics { get; set; }

        public IWorkerManager Manager { get; set; }

        public void Start() {
            _reset.Reset();
            ThreadPool.RegisterWaitForSingleObject(_reset, new WaitOrTimerCallback(ProcessEvent),
                null, Interval, true);
        }

        public int Interval { get; set; }

        public string Address { get; set; }

        private void ProcessEvent(object state, bool timeout) {
            if (!timeout) return;
            MethodHelper.TryLog(Compose);
            ThreadPool.RegisterWaitForSingleObject(_reset, new WaitOrTimerCallback(ProcessEvent),
                null, Interval, true);
        }

        private CompositionContainer Container {
            get {
                var catalog = new AggregateCatalog();

                catalog.Catalogs.Add(new AssemblyCatalog(typeof(MefObserver).Assembly));
                var folders = Address.Split(';');
                foreach (var folder in folders)
                    catalog.Catalogs.Add(new DirectoryCatalog(folder));

                var container = new CompositionContainer(catalog);

                return container;
            }
        }

        private void Compose() {
            try {
                Container.ComposeParts(this);
                PublishToManager();
            } catch (Exception ex) {
                ESBLogger.Log(ex);
            }
        }

        private void PublishToManager() {
            MethodHelper.TryLog(() =>
            {
                var topics = _topics.ToList();
                var emptyGuid = Guid.Empty;
                topics.ForEach(topic =>
                    topic.ID = topic.ID == emptyGuid ? Guid.NewGuid() : topic.ID);

                var currentTopics = Manager.Workers.ToList();

                var addedTopics = topics.Where(_c => !currentTopics.Exists(c => c.TopicID == _c.ID 
                    && AssemblyName.GetAssemblyName(c.GetType().Assembly.CodeBase).Version.ToString() ==
                    AssemblyName.GetAssemblyName(_c.GetType().Assembly.CodeBase).Version.ToString()));
                var deletedWorkers = currentTopics.Where(c => !_topics.Any(_c => c.TopicID == _c.ID) && !_classIDs.Contains(c.TopicID));

                foreach (var topic in addedTopics) {
                    var worker = topic as IWorker;
                    if (worker == null) continue;
                    worker.TopicID = topic.ID;
                    if (_classIDs.Contains(topic.ID)) Manager.Detach(worker);
                    if (!_classIDs.Contains(topic.ID)) _classIDs.Add(topic.ID);
                    Manager.Attach(worker);
                }
                Manager.Detach(deletedWorkers.Where(e => _classIDs.Contains(e.TopicID)));
            });
        }

        public void Stop() {
            _reset.Set();
        }

        public void Init() {
            Compose();  
        }

        #endregion
    }
}
